/*
 * Copyright (C) 2008 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect.testing.testers;

import static com.google.common.collect.testing.Helpers.getMethod;
import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS;
import static com.google.common.collect.testing.features.CollectionSize.ONE;
import static com.google.common.collect.testing.features.CollectionSize.ZERO;
import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_ADD_WITH_INDEX;
import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_REMOVE_WITH_INDEX;
import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_SET;
import static java.util.Collections.emptyList;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.collect.testing.Helpers;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.features.ListFeature;
import com.google.common.testing.SerializableTester;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.junit.Ignore;

/**
 * A generic JUnit test which tests {@code subList()} operations on a list. Can't be invoked
 * directly; please see {@link com.google.common.collect.testing.ListTestSuiteBuilder}.
 *
 * @author Chris Povirk
 */
@SuppressWarnings("unchecked") // too many "unchecked generic array creations"
@GwtCompatible(emulated = true)
@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests.
public class ListSubListTester<E> extends AbstractListTester<E>
{
    public void testSubList_startNegative()
    {
        try
        {
            getList().subList(-1, 0);
            fail("subList(-1, 0) should throw");
        }
        catch (IndexOutOfBoundsException expected)
        {
        }
    }

    public void testSubList_endTooLarge()
    {
        try
        {
            getList().subList(0, getNumElements() + 1);
            fail("subList(0, size + 1) should throw");
        }
        catch (IndexOutOfBoundsException expected)
        {
        }
    }

    public void testSubList_startGreaterThanEnd()
    {
        try
        {
            getList().subList(1, 0);
            fail("subList(1, 0) should throw");
        }
        catch (IndexOutOfBoundsException expected)
        {
        }
        catch (IllegalArgumentException expected)
        {
            /*
             * The subList() docs claim that this should be an
             * IndexOutOfBoundsException, but many JDK implementations throw
             * IllegalArgumentException:
             * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4506427
             */
        }
    }

    public void testSubList_empty()
    {
        assertEquals("subList(0, 0) should be empty", emptyList(), getList().subList(0, 0));
    }

    public void testSubList_entireList()
    {
        assertEquals(
                "subList(0, size) should be equal to the original list",
                getList(),
                getList().subList(0, getNumElements()));
    }

    @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX)
    @CollectionSize.Require(absent = ZERO)
    public void testSubList_subListRemoveAffectsOriginal()
    {
        List<E> subList = getList().subList(0, 1);
        subList.remove(0);
        List<E> expected = Arrays.asList(createSamplesArray()).subList(1, getNumElements());
        expectContents(expected);
    }

    @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX)
    @CollectionSize.Require(absent = ZERO)
    public void testSubList_subListClearAffectsOriginal()
    {
        List<E> subList = getList().subList(0, 1);
        subList.clear();
        List<E> expected = Arrays.asList(createSamplesArray()).subList(1, getNumElements());
        expectContents(expected);
    }

    @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX)
    public void testSubList_subListAddAffectsOriginal()
    {
        List<E> subList = getList().subList(0, 0);
        subList.add(e3());
        expectAdded(0, e3());
    }

    @ListFeature.Require(SUPPORTS_SET)
    @CollectionSize.Require(absent = ZERO)
    public void testSubList_subListSetAffectsOriginal()
    {
        List<E> subList = getList().subList(0, 1);
        subList.set(0, e3());
        List<E> expected = Helpers.copyToList(createSamplesArray());
        expected.set(0, e3());
        expectContents(expected);
    }

    @ListFeature.Require(SUPPORTS_SET)
    @CollectionSize.Require(absent = ZERO)
    public void testSubList_originalListSetAffectsSubList()
    {
        List<E> subList = getList().subList(0, 1);
        getList().set(0, e3());
        assertEquals(
                "A set() call to a list after a sublist has been created "
                        + "should be reflected in the sublist",
                Collections.singletonList(e3()),
                subList);
    }

    @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_subListRemoveAffectsOriginalLargeList()
    {
        List<E> subList = getList().subList(1, 3);
        subList.remove(e2());
        List<E> expected = Helpers.copyToList(createSamplesArray());
        expected.remove(2);
        expectContents(expected);
    }

    @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_subListAddAtIndexAffectsOriginalLargeList()
    {
        List<E> subList = getList().subList(2, 3);
        subList.add(0, e3());
        expectAdded(2, e3());
    }

    @ListFeature.Require(SUPPORTS_SET)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_subListSetAffectsOriginalLargeList()
    {
        List<E> subList = getList().subList(1, 2);
        subList.set(0, e3());
        List<E> expected = Helpers.copyToList(createSamplesArray());
        expected.set(1, e3());
        expectContents(expected);
    }

    @ListFeature.Require(SUPPORTS_SET)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_originalListSetAffectsSubListLargeList()
    {
        List<E> subList = getList().subList(1, 3);
        getList().set(1, e3());
        assertEquals(
                "A set() call to a list after a sublist has been created "
                        + "should be reflected in the sublist",
                Arrays.asList(e3(), e2()),
                subList);
    }

    public void testSubList_ofSubListEmpty()
    {
        List<E> subList = getList().subList(0, 0).subList(0, 0);
        assertEquals("subList(0, 0).subList(0, 0) should be an empty list", emptyList(), subList);
    }

    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_ofSubListNonEmpty()
    {
        List<E> subList = getList().subList(0, 2).subList(1, 2);
        assertEquals(
                "subList(0, 2).subList(1, 2) "
                        + "should be a single-element list of the element at index 1",
                Collections.singletonList(getOrderedElements().get(1)),
                subList);
    }

    @CollectionSize.Require(absent = {ZERO})
    public void testSubList_size()
    {
        List<E> list = getList();
        int size = getNumElements();
        assertEquals(size, list.subList(0, size).size());
        assertEquals(size - 1, list.subList(0, size - 1).size());
        assertEquals(size - 1, list.subList(1, size).size());
        assertEquals(0, list.subList(size, size).size());
        assertEquals(0, list.subList(0, 0).size());
    }

    @CollectionSize.Require(absent = {ZERO})
    public void testSubList_isEmpty()
    {
        List<E> list = getList();
        int size = getNumElements();
        for (List<E> subList :
                Arrays.asList(
                        list.subList(0, size),
                        list.subList(0, size - 1),
                        list.subList(1, size),
                        list.subList(0, 0),
                        list.subList(size, size)))
        {
            assertEquals(subList.size() == 0, subList.isEmpty());
        }
    }

    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_get()
    {
        List<E> list = getList();
        int size = getNumElements();
        List<E> copy = list.subList(0, size);
        List<E> head = list.subList(0, size - 1);
        List<E> tail = list.subList(1, size);
        assertEquals(list.get(0), copy.get(0));
        assertEquals(list.get(size - 1), copy.get(size - 1));
        assertEquals(list.get(1), tail.get(0));
        assertEquals(list.get(size - 1), tail.get(size - 2));
        assertEquals(list.get(0), head.get(0));
        assertEquals(list.get(size - 2), head.get(size - 2));
        for (List<E> subList : Arrays.asList(copy, head, tail))
        {
            for (int index : Arrays.asList(-1, subList.size()))
            {
                try
                {
                    subList.get(index);
                    fail("expected IndexOutOfBoundsException");
                }
                catch (IndexOutOfBoundsException expected)
                {
                }
            }
        }
    }

    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_contains()
    {
        List<E> list = getList();
        int size = getNumElements();
        List<E> copy = list.subList(0, size);
        List<E> head = list.subList(0, size - 1);
        List<E> tail = list.subList(1, size);
        assertTrue(copy.contains(list.get(0)));
        assertTrue(head.contains(list.get(0)));
        assertTrue(tail.contains(list.get(1)));
        // The following assumes all elements are distinct.
        assertTrue(copy.contains(list.get(size - 1)));
        assertTrue(head.contains(list.get(size - 2)));
        assertTrue(tail.contains(list.get(size - 1)));
        assertFalse(head.contains(list.get(size - 1)));
        assertFalse(tail.contains(list.get(0)));
    }

    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_indexOf()
    {
        List<E> list = getList();
        int size = getNumElements();
        List<E> copy = list.subList(0, size);
        List<E> head = list.subList(0, size - 1);
        List<E> tail = list.subList(1, size);
        assertEquals(0, copy.indexOf(list.get(0)));
        assertEquals(0, head.indexOf(list.get(0)));
        assertEquals(0, tail.indexOf(list.get(1)));
        // The following assumes all elements are distinct.
        assertEquals(size - 1, copy.indexOf(list.get(size - 1)));
        assertEquals(size - 2, head.indexOf(list.get(size - 2)));
        assertEquals(size - 2, tail.indexOf(list.get(size - 1)));
        assertEquals(-1, head.indexOf(list.get(size - 1)));
        assertEquals(-1, tail.indexOf(list.get(0)));
    }

    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testSubList_lastIndexOf()
    {
        List<E> list = getList();
        int size = list.size();
        List<E> copy = list.subList(0, size);
        List<E> head = list.subList(0, size - 1);
        List<E> tail = list.subList(1, size);
        assertEquals(size - 1, copy.lastIndexOf(list.get(size - 1)));
        assertEquals(size - 2, head.lastIndexOf(list.get(size - 2)));
        assertEquals(size - 2, tail.lastIndexOf(list.get(size - 1)));
        // The following assumes all elements are distinct.
        assertEquals(0, copy.lastIndexOf(list.get(0)));
        assertEquals(0, head.lastIndexOf(list.get(0)));
        assertEquals(0, tail.lastIndexOf(list.get(1)));
        assertEquals(-1, head.lastIndexOf(list.get(size - 1)));
        assertEquals(-1, tail.lastIndexOf(list.get(0)));
    }

    @CollectionFeature.Require(SERIALIZABLE_INCLUDING_VIEWS)
    public void testReserializeWholeSubList()
    {
        SerializableTester.reserializeAndAssert(getList().subList(0, getNumElements()));
    }

    @CollectionFeature.Require(SERIALIZABLE_INCLUDING_VIEWS)
    public void testReserializeEmptySubList()
    {
        SerializableTester.reserializeAndAssert(getList().subList(0, 0));
    }

    @CollectionFeature.Require(SERIALIZABLE_INCLUDING_VIEWS)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testReserializeSubList()
    {
        SerializableTester.reserializeAndAssert(getList().subList(0, 2));
    }

    /**
     * Returns the {@link Method} instance for {@link #testSubList_originalListSetAffectsSubList()} so
     * that tests of {@link CopyOnWriteArrayList} can suppress them with {@code
     * FeatureSpecificTestSuiteBuilder.suppressing()} until <a
     * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6570631">Sun bug 6570631</a> is fixed.
     */
    @GwtIncompatible // reflection
    public static Method getSubListOriginalListSetAffectsSubListMethod()
    {
        return getMethod(ListSubListTester.class, "testSubList_originalListSetAffectsSubList");
    }

    /**
     * Returns the {@link Method} instance for {@link
     * #testSubList_originalListSetAffectsSubListLargeList()} ()} so that tests of {@link
     * CopyOnWriteArrayList} can suppress them with {@code
     * FeatureSpecificTestSuiteBuilder.suppressing()} until <a
     * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6570631">Sun bug 6570631</a> is fixed.
     */
    @GwtIncompatible // reflection
    public static Method getSubListOriginalListSetAffectsSubListLargeListMethod()
    {
        return getMethod(ListSubListTester.class, "testSubList_originalListSetAffectsSubListLargeList");
    }

    /**
     * Returns the {@link Method} instance for {@link
     * #testSubList_subListRemoveAffectsOriginalLargeList()} so that tests of {@link
     * CopyOnWriteArrayList} can suppress it with {@code
     * FeatureSpecificTestSuiteBuilder.suppressing()} until <a
     * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6570575">Sun bug 6570575</a> is fixed.
     */
    @GwtIncompatible // reflection
    public static Method getSubListSubListRemoveAffectsOriginalLargeListMethod()
    {
        return getMethod(ListSubListTester.class, "testSubList_subListRemoveAffectsOriginalLargeList");
    }

    /*
     * TODO: perform all List tests on subList(), but beware infinite recursion
     */
}
